package com.ruoyi.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.config.SearchRequestData;
import com.ruoyi.entity.Article;
import com.ruoyi.netty.ElasticSearchService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author hlf
 * @createTime 2025/2/10 16:24
 * @description
 */
@Service
@Slf4j
public class ElasticSearchServiceImpl implements ElasticSearchService {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient; // 用于执行自定义 SearchRequest


    @Override
    public List<Article> searchWithHighlight(SearchRequestData request) {
        String term = request.getTerm();
        String numBegin = request.getNumBegin();
        String numEnd = request.getNumEnd();
        int page = request.getPageNum();
        int size = request.getPageSize();
        String sortField = request.getSortField();
        // 必须大写
        String sortOrder = request.getSortOrder();
        // 构建查询条件
        //MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(term, "title", "content");
        // 创建 BoolQueryBuilder
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if(StrUtil.isNotEmpty(term)){
            // 添加 must 条件
            boolQueryBuilder.must(QueryBuilders.matchQuery("content", term).boost(2.0f));
            boolQueryBuilder.should(QueryBuilders.matchQuery("title", term));
        }
        if(StrUtil.isNotEmpty(numBegin)&& StrUtil.isNotEmpty(numEnd)){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(30).lte(90));
        }
        // 构建高亮显示
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .field("title") // 高亮 title 字段
                .field("content") // 高亮 content 字段
                .preTags("<em>") // 高亮前缀
                .postTags("</em>"); // 高亮后缀

        // 构建分页
        PageRequest pageRequest = PageRequest.of(page - 1, size);

        // 构建排序
        org.elasticsearch.search.sort.SortBuilder<?> sortBuilder = SortBuilders.fieldSort(sortField)
                .order(SortOrder.valueOf(sortOrder));

        // 构建 NativeSearchQuery
        /**
         * 在 Elasticsearch 中，添加排序字段后，_score 变为 NaN 是一个常见问题。
         * 这是因为 Elasticsearch 的默认行为是：如果指定了排序字段，且没有显式要求计算 _score，Elasticsearch 会忽略相关性分数的计算。
         **/
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withHighlightBuilder(highlightBuilder)
                .withPageable(pageRequest)
                .withSort(sortBuilder) // 添加排序
                .withTrackScores(true) // 启用分数计算
                .build();

        // 执行查询
        SearchHits<Article> searchHits = elasticsearchRestTemplate.search(searchQuery, Article.class);

        // 处理高亮结果
        return searchHits.stream()
                .map(hit -> {
                    Article article = hit.getContent();

                    // 设置高亮字段
                    if (hit.getHighlightFields().containsKey("title")) {
                        article.setTitle(hit.getHighlightFields().get("title").get(0));
                    }
                    if (hit.getHighlightFields().containsKey("content")) {
                        article.setContent(hit.getHighlightFields().get("content").get(0));
                    }

                    return article;
                })
                .collect(Collectors.toList());
    }

    /**
     * @description 单个新增
     * @author hlf
     * @date 2025/2/11 15:58
     **/
    @Override
    public void articleAddEs(String payload) {
        log.info(payload);
        Article article = JSONUtil.toBean(payload, Article.class);
        elasticsearchRestTemplate.save(article);

    }

    /**
     * @description 新增全部
     * @author hlf
     * @date 2025/2/11 16:01
     **/
    @Override
    public void articleMessageAll(String payload) {
        List<Article> article = JSONUtil.toList(payload, Article.class);
        elasticsearchRestTemplate.save(article);
    }


    /**
     * @description 测试得分
     * @author hlf
     * @date 2025/2/11 14:20
     **/
    @Override
    public List<Article> searchScore(SearchRequestData request) {
        String term = request.getTerm();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.matchQuery("content", term));
        boolQueryBuilder.should(QueryBuilders.matchQuery("title", term));

        // 构建高亮显示
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .field("title") // 高亮 title 字段
                .field("content") // 高亮 content 字段
                .preTags("<em>") // 高亮前缀
                .postTags("</em>"); // 高亮后缀

        // 构建排序
        org.elasticsearch.search.sort.SortBuilder<?> sortBuilder = SortBuilders.fieldSort("price")
                .order(SortOrder.valueOf("DESC"));

        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withHighlightBuilder(highlightBuilder)
                .withSort(sortBuilder)
                .withTrackScores(true) // 启用分数计算
                .build();

        // 执行查询
        SearchHits<Article> searchHits = elasticsearchRestTemplate.search(searchQuery, Article.class);
        for (SearchHit<Article> searchHit : searchHits) {
            Article article = searchHit.getContent();
            log.info(article.toString());
        }

        return null;
    }




    /**
     * @description 适配前端页面的接口，返回es分页高亮
     * @author hlf
     * @date 2025/2/28 15:00
     **/
    @Override
    public TableDataInfo highlightList(SearchRequestData request) throws IOException {
        String term = request.getTerm();
        String numBegin = request.getNumBegin();
        String numEnd = request.getNumEnd();
        int page = request.getPageNum();
        int size = request.getPageSize();
        String sortField = request.getSortField();
        // 必须大写
        String sortOrder = request.getSortOrder();
        // 构建查询条件
        // 创建 BoolQueryBuilder
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if(StrUtil.isNotEmpty(term)){
            // 添加 must 条件
            boolQueryBuilder.must(QueryBuilders.matchQuery("content", term).boost(2.0f));
            boolQueryBuilder.should(QueryBuilders.matchQuery("title", term));
        }
        if(StrUtil.isNotEmpty(numBegin)&& StrUtil.isNotEmpty(numEnd)){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(numBegin).lte(numEnd));
        }
        if(StrUtil.isNotEmpty(numBegin)&& StrUtil.isNotEmpty(numEnd)){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(numBegin).lte(numEnd));
        }
        // 构建高亮显示
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .field("title") // 高亮 title 字段
                .field("content") // 高亮 content 字段
                .preTags("<em>") // 高亮前缀
                .postTags("</em>"); // 高亮后缀

        // 构建 NativeSearchQuery
        /**
         * 在 Elasticsearch 中，添加排序字段后，_score 变为 NaN 是一个常见问题。
         * 这是因为 Elasticsearch 的默认行为是：如果指定了排序字段，且没有显式要求计算 _score，Elasticsearch 会忽略相关性分数的计算。
         **/
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                .query(boolQueryBuilder)
                .sort(sortField, SortOrder.valueOf(sortOrder))
                .from((page - 1) * size) // Elasticsearch 的 from 是从 0 开始的
                .size(size)
                .trackScores(true)
                .highlighter(highlightBuilder);
        SearchRequest searchRequest = new SearchRequest("article");
        searchRequest.source(searchSourceBuilder);

        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 处理查询结果
        List<Article> resultList = new ArrayList<>();
        org.elasticsearch.search.SearchHits hits = searchResponse.getHits();
        for (org.elasticsearch.search.SearchHit hit : hits.getHits()) {
            // 将 Elasticsearch 的 JSON 数据映射到 Article 对象
            Article article = BeanUtil.toBean(hit.getSourceAsMap(), Article.class);
            article.setSuggestionText(StrUtil.join(",",article.getSuggestion().getInput()));
            // 处理高亮字段
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields.containsKey("title")) {
                article.setTitle(highlightFields.get("title").fragments()[0].toString());
            }
            if (highlightFields.containsKey("content")) {
                article.setContent(highlightFields.get("content").fragments()[0].toString());
            }
            resultList.add(article);

        }
        return new TableDataInfo(resultList, (int)hits.getTotalHits().value);
    }

    @Override
    public void articleMessageDel(String payload) {
        Article article = JSONUtil.toBean(payload, Article.class);
        elasticsearchRestTemplate.delete(article);
    }

    /**
     * @description 在 Elasticsearch 7.x 版本中，通常使用 SearchSourceBuilder 来构建查询，
     * 而不是像之前那样直接使用 NativeQueryBuilder（该类主要用于低级 API）。SearchSourceBuilder 提供了更丰富和灵活的 API 来构建复杂的查询请求。
     * @author hlf
     * @date 2025/3/3 17:05
     * @param request
     * @return java.util.List<com.ruoyi.entity.Article>
     **/
    @Override
    public List<Article> getArticleSuggestionText(SearchRequestData request) {
        List<Article> suggestions = new ArrayList<>();
        if (StrUtil.isEmpty(request.getTerm())) {
            return suggestions;
        }

        // 构建自动补全建议
        CompletionSuggestionBuilder suggestionBuilder = SuggestBuilders
                .completionSuggestion("suggestion")
                .prefix(request.getTerm())
                .size(5)
                .skipDuplicates(true); // 跳过重复的建议项

        // 构建 SuggestBuilder
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("suggestion", suggestionBuilder);

        // 构建 SearchSourceBuilder
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.suggest(suggestBuilder);
        searchSourceBuilder.fetchSource(new String[]{"id", "title", "content"}, null); // 指定返回的字段

        // 构建 SearchRequest
        SearchRequest searchRequest = new SearchRequest("article");
        searchRequest.source(searchSourceBuilder);

        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            // 处理搜索结果
            Suggest suggest = searchResponse.getSuggest();
            if (suggest != null) {
                CompletionSuggestion completionSuggestion = suggest.getSuggestion("suggestion");
                for (CompletionSuggestion.Entry.Option option : completionSuggestion.getOptions()) {
                    Map<String, Object> sourceAsMap = option.getHit().getSourceAsMap();
                    if (sourceAsMap != null) {
                        Article article = BeanUtil.toBean(sourceAsMap,Article.class);
                        suggestions.add(article);
                    }
                }
            }
        } catch (IOException e) {
            // 更详细的错误处理逻辑，例如重试或记录日志
            e.printStackTrace();
        }

        return suggestions;
    }


}
